package com.dimpt.cloud.userauth.impl;


import com.dimpt.cloud.dataservice.aaa.model.Permit;
import com.dimpt.cloud.dataservice.aaa.model.Token;
import com.dimpt.cloud.dataservice.aaa.model.User;
import com.dimpt.cloud.common.config.ResponseInfo;
import com.dimpt.cloud.common.exception.FailedException;
import com.dimpt.cloud.dataservice.aaa.dao.PermitMapper;
import com.dimpt.cloud.dataservice.aaa.dao.TokenMapper;
import com.dimpt.cloud.dataservice.aaa.dao.UserMapper;
import com.dimpt.cloud.userauth.model.SessionSO;
import com.dimpt.cloud.userauth.model.TokenSO;
import com.dimpt.cloud.userauth.service.IAuthService;
import com.dimpt.cloud.userauth.service.ISessionCacheService;
import com.dimpt.cloud.userauth.model.UserSO;
import com.dimpt.cloud.common.utils.DateUtils;
import com.dimpt.cloud.common.utils.MD5Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 鉴权操作类
 *
 * 注意：目前，所有的鉴权信息都放在数据库中，对于高并发操作会对数据库产生很大冲击，后期考虑放到内存数据库，减低数据库负荷
 * 内存数据库考虑采用 HezelCast
 */
@Service
public class AuthServiceImpl implements ISessionCacheService,IAuthService {

    // Token存活时长，单位为毫秒，默认为30分钟
    @Value("${auth.token.delay-value}")
    private long tokenDelayValue;

    @Value("${auth.token.without-urls}")
    private String PERMIT_URI_STR;

    //存储登录成功的用户信息,Map<String<token>, Account<account>>
    private static Map<String,SessionSO> accounts = new HashMap<>();

    private Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
//
    @Autowired
    UserMapper userDao;

    @Autowired
    TokenMapper tokenDao;

    @Autowired
    PermitMapper permitDao;



    public AuthServiceImpl() {

    }

    /**
     * 登录
     * 首先根据username查询 如果查询到了结果
     * 那么校验密码是否一致 如果一致的话 那么更新time和session
     * 最终存入session
     *
     * @param loginMap
     * @return String
     */
    public UserSO login(Map<String,String> loginMap) {

        List<User> accounts = userDao.getUserByUsername(loginMap.get("userName"));
        //用户不存在
        if (accounts.size() == 0) {
            throw new FailedException(ResponseInfo.USERNAME_ERROR.getErrorCode(), ResponseInfo.USERNAME_ERROR.getErrorMsg());
        }
        User account = accounts.get(0);
        //用户密码错误
        if (!(account.getPwdHash() != null && account.getPwdHash().equals(MD5Utils.getResult(loginMap.get("password")))))
            throw new FailedException(ResponseInfo.PASSWORD_ERROR.getErrorCode(), ResponseInfo.PASSWORD_ERROR.getErrorMsg());

        List<Token> tokens = new ArrayList<>();
        if ((tokens = tokenDao.getTokenByUsername(account.getUserName())).size() > 0) {  //登录账户的token已存在，删除token
            tokenDao.deleteTokenById(tokens.get(0).getId());
        }
        Token token;
        String tokenId = UUID.randomUUID().toString().replace("-", "");
        token = new Token();
        token.setId(tokenId);
        token.setUserName(account.getUserName());
        token.setCreatedAt(DateUtils.getDate());
        token.setUpdatedAt(DateUtils.getDate());
        token.setRole(account.getDefaultRole());
        token.setClientAddress(loginMap.get("clientAddress"));
        token.setClientPort(loginMap.get("clientPort"));
        token.setServerAddress(loginMap.get("serverAddress"));
        token.setServerPort(loginMap.get("serverPort"));
        //插入会话失败
        if (tokenDao.addToken(token) == 0)
            throw new FailedException(ResponseInfo.AUTH_FAILED.getErrorCode(), ResponseInfo.AUTH_FAILED.getErrorMsg());

        //存储用户信息至内存，供控制器调用
        SessionSO sessionSO = new SessionSO(account,token);
        sessionSO.setId(token.getId());
        this.setSession(sessionSO);

        //生成UserSO对象
        UserSO user =  new UserSO(account);
        user.setToken(token.getId());

        return user;
    }

    /**
     * 用户注销
     */
    public void logout(String token) {
        //TODO:
        //删除该token
        tokenDao.deleteTokenById(token);
        cleanSession(token);
    }

    @Override
    public UserSO checkUserIsLogin(String username) {
        List<User> user =  userDao.getUserByUsername(username);
        UserSO so = new UserSO(user.get(0));

        List<Token> token ;
        if ((token = tokenDao.getTokenByUsername(so.getUserName())).size() > 0) {  //登录账户的token已存在，删除token
            //   tokenDao.deleteTokenById(token.getId());
            so.setToken(token.get(0).getId());
        }
        return so;
    }

    /**
     *
     * 判断给定的token是否有效
     * 从数据库中读取token信息，如果读取不到，则返回null，如果读取到了，则返回account_id
     *
     * @param tokenId
     * @return
     */
    public TokenSO validateToken(String tokenId) {
        //TODO:增加token过时判断，判断t_token表中的updated_at是否过期
        List<Token> tokens =null;
        if ((tokens = tokenDao.getTokenById(tokenId)).size() > 0){
            //TODO:更新t_token表的updated_at
            Token token = tokens.get(0);

            //该token值对应的上一次请求时间,转long型
            long update = DateUtils.convert2long(token.getUpdatedAt(), DateUtils.TIME_FORMAT);

            long now = DateUtils.convert2long(DateUtils.getDate(), DateUtils.TIME_FORMAT);

            if((now - update) > tokenDelayValue){   //token已过期
                //删除该token
                tokenDao.deleteTokenById(tokenId);
                cleanSession(tokenId);
                return null;
            }
            //更新本次token请求的时间
            token.setUpdatedAt(DateUtils.getDate());
            tokenDao.updateTokenById(token);
            TokenSO tokenSO = new TokenSO(token);
            return tokenSO;
        } else {
            return null;
        }
    }

    /**
     * 判断给定的uri是否不需要鉴权，直接访问
     *
     * @param method
     * @param uri
     *
     * @return
     */
    public boolean isPermit(String method,String uri) {

        boolean flag = false;
        String[] PERMIT_URI = PERMIT_URI_STR.split(",");
        for(int i = 0;i<PERMIT_URI.length;i++){

            if(PERMIT_URI[i] == uri || PERMIT_URI[i].equals(uri) ){
                flag = true;
                break;
            }else if(PERMIT_URI[i].indexOf("/*") != -1){
                String new_rui = PERMIT_URI[i].substring(0, PERMIT_URI[i].lastIndexOf("/*"));
                if(uri.indexOf(new_rui) != -1){     //检测url是否为激活的动态url
                    flag = true;
                    break;
                }
            }
        }
        return flag;
    }

    /**
     * 判断给定的角色能否以特定的http method访问指定的uri
     *
     * @param role
     * @param method
     * @param uri
     * @return
     */
    public boolean auth(String role,String method,String uri) {
        //TODO:增加用户、权限的代码
        Permit permit = new Permit();
        permit.setRole(role);
        permit.setHttpMethod(method);
        permit.setUri(uri);
        List<Permit> permits = permitDao.checkPermit(permit);
        if(permits.size()>0){
            return true;
        }else{
            return false;
        }
    }

    @Override
    public void cleanSession(String sessionId) {
        if ((sessionId != null)) {
            accounts.remove(sessionId);
        }
    }

    @Override
    public void cleanAll() {
        accounts.clear();
    }

    @Override
    public SessionSO getSession(String sessonId) {
        if ((sessonId != null)) {
            if(accounts.get(sessonId) != null){     //对于负载的服务器，登录和应用不在一个服务器，暂存无法共享
              //  System.out.println(accounts.get(sessonId));
                return accounts.get(sessonId);
            }else{      //根据token查询用户信息，暂存
                List<Token> token1 = tokenDao.getTokenById(sessonId);
                if(token1.size() == 0){
                 //   System.out.println("username is null");
                    return null;
                }else{
                 //   System.out.println("username: " + token1.getUsername());
                    List<User> user = userDao.getUserByUsername(token1.get(0).getUserName());
                    SessionSO sessionSO = new SessionSO(user.get(0),token1.get(0));
                    sessionSO.setId(sessonId);
                    setSession(sessionSO);
                    return sessionSO;
                }
            }
        }
        return null;
    }

    @Override
    public void setSession(SessionSO session) {
        accounts.put(session.getId(),session);
    }
}